﻿using System;
using System.Collections.Generic;
using System.IO;

namespace DeepZoomPivotConstructor.VisualLib
{
    /// <summary>
    /// Base class for a single image.
    /// </summary>
    [Serializable]
    abstract public class Visual : IEnumerable<Visual>
    {
        public Visual()
        {
        }

        #region Basic Location Info
        /// <summary>
        /// Get the current width of the image (in inches).
        /// </summary>
        public abstract double Width
        {
            get;
        }

        /// <summary>
        /// Get the current height of the image (in inches).
        /// </summary>
        public abstract double Height
        {
            get;
        }

        /// <summary>
        /// Get the curretn x position (in inches).
        /// </summary>
        public abstract double XPosition
        {
            get;
            set;
        }

        /// <summary>
        /// Get current y positition (in inches).
        /// </summary>
        public abstract double YPosition
        {
            get;
            set;
        }

        /// <summary>
        /// Summary of above - more efficient as it doesn't
        /// require lots of re-calc.
        /// </summary>
        public class BoundingBox
        {
            public double XPosition { get; set; }
            public double YPosition { get; set; }
            public double Width { get; set; }
            public double Height { get; set; }
            public BoundingBox(double x, double y, double wid, double height)
            {
                XPosition = x;
                YPosition = y;
                Width = wid;
                Height = height;
            }
        }

        /// <summary>
        /// Returns x, y, width, and height in one go - most efficent way if
        /// you need to calculate more than one of these guys.
        /// </summary>
        public virtual BoundingBox VisualBoundingBox
        {
            get { return new BoundingBox(XPosition, YPosition, Width, Height); }
        }

        /// <summary>
        /// Enlarge or shrink the image. Keeps the apsect ratio.
        /// </summary>
        /// <param name="factor">If greater 1 then it gets larger, If less than 1 it gets smaller.</param>
        public abstract void Scale(double factor);

        /// <summary>
        /// Makes sure no single dimension is larger than max, and
        /// rescales if it is, maintaining the aspect ratio.
        /// </summary>
        /// <param name="maxDimension"></param>
        public virtual void ScaleToMaxSize(double maxDimension)
        {
            var bb = VisualBoundingBox;
            double bigest = bb.Width;
            if (bb.Height > bigest)
            {
                bigest = bb.Height;
            }
            if (bigest > maxDimension)
            {
                double factor = maxDimension / bigest;
                Scale(factor);
            }
        }

        /// <summary>
        /// Scales the image to a specificed width, maintaining
        /// aspect ratio.
        /// </summary>
        /// <param name="newWidth"></param>
        public virtual void ScaleToWidth(double newWidth)
        {
            double factor = newWidth / Width;
            Scale(factor);
        }

        /// <summary>
        /// Change the z index by some number
        /// </summary>
        /// <param name="delta"></param>
        public abstract void IncZIndex(int delta);

        /// <summary>
        /// Short cut to inc by one.
        /// </summary>
        public void IncZIndex()
        {
            IncZIndex(1);
        }

        /// <summary>
        /// Returns the z index of this visual. The larger the z order, the closer to the front
        /// the image is drawn. So, a zindex of 0 is the background image (furthest back).
        /// </summary>
        public abstract int ZIndex
        {
            get;
        }

        #endregion

        /// <summary>
        /// Holds extra info that might be attached to this Visual. Title, etc.
        /// This information is often used as header, etc., by rendering
        /// code. How it is used is totally up to the IRenderItem that is
        /// rendering a sub-item... what info that is put in it is totally up
        /// to the IRenderItem that is rendering the item.
        /// </summary>
        [Serializable]
        public class VisualInfo
        {
            /// <summary>
            /// The one line title of this image
            /// </summary>
            public string Title { get; set; }
            /// <summary>
            /// Extra info to be attached (in human readable form!) to the image.
            /// </summary>
            public string[] Info { get; set; }
        }

        /// <summary>
        /// Get or set the information assoicated with this visual
        /// </summary>
        public VisualInfo Info { get; set; }

        /// <summary>
        /// Returns all the sceen graph nodes that make this guy up.
        /// Each one of these points to an image that can be rendered by
        /// the deepzoom library.
        /// </summary>
        public abstract List<SceneGraphSceneNode> SGNodeList
        {
            get;
        }

        #region HotSpots



        #endregion

        #region Composable Settings and Information
        private ComposableInfo composableInfo = null;

        /// <summary>
        /// Set to true if you would like the images here and below to be treated as a
        /// single composition.
        /// </summary>
        public bool IsCompositionUnit
        {
            get { return composableInfo != null; }
        }

        /// <summary>
        /// Returns the info about the composable unit.
        /// </summary>
        public ComposableInfo CompositionInfo
        {
            get { return composableInfo; }
        }

        /// <summary>
        /// Keep track of any name given for a composable unit
        /// </summary>
        public string ComposableUnitName { get; private set; }

        /// <summary>
        /// Make this particular visual a composable unit.
        /// </summary>
        /// <param name="metaData"></param>
        /// <param name="cuName">Name to give the composable unit, random name if null</param>
        public void SetAsComposableUnit(object metaData, string cuName = null)
        {
            composableInfo = new ComposableInfo();
            composableInfo.MetaData = metaData;
            if (cuName != null)
            {
                ComposableUnitName = cuName;
            }
            else
            {
                ComposableUnitName = Path.GetFileNameWithoutExtension(Path.GetRandomFileName());
            }
        }
        #endregion

        #region Enumerators
        /// <summary>
        /// Gets an enumerator that will return all the sub-visuals.
        /// </summary>
        /// <returns></returns>
        public virtual System.Collections.Generic.IEnumerator<Visual> GetEnumerator()
        {
            yield return this;
        }

        /// <summary>
        /// Return an enumerator that will find only those guys that don't pass a certian test - those it will
        /// return, but it won't look "into" them.
        /// </summary>
        /// <param name="test">Return true to prevent further decent into a tree</param>
        /// <returns></returns>
        public IEnumerable<Visual> GetHierarchicalEnumerator(Func<Visual, bool> test)
        {
            return new VisualHierarchyConditional(this, test);
        }

        /// <summary>
        /// Makes it possible to list all the various visual elements. It doesn't go "down" one
        /// when it finds a match with the function. THis is deep -- it will flatten the tree
        /// until it hits a certian condition and then it won't try to go any deeper.
        /// </summary>
        protected class VisualHierarchyConditional : IEnumerable<Visual>
        {
            private Visual _root;
            private Func<Visual, bool> _test;

            public VisualHierarchyConditional(Visual root, Func<Visual, bool> test)
            {
                _test = test;
                _root = root;
            }

            /// <summary>
            /// Gets the next guy, and goes no deeper if it satisfies the condition too.
            /// </summary>
            /// <returns></returns>
            public IEnumerator<Visual> GetEnumerator()
            {
                if (_test(_root))
                {
                    yield return _root;
                }
                else
                {
                    foreach (var v in _root)
                    {
                        if (v == _root)
                        {
                            yield return v;
                        }
                        else
                        {
                            VisualHierarchyConditional matcher = new VisualHierarchyConditional(v, _test);
                            foreach (var vsub in matcher)
                            {
                                yield return vsub;
                            }
                        }
                    }
                }
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return this.GetEnumerator();
            }
        }

        /// <summary>
        /// So far not required, so not implemented! :-)
        /// </summary>
        /// <returns></returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}
